﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web;
using System.Security.Principal;
using Mindfor.Web.Data;

namespace Mindfor.Web
{
	/// <summary>
	/// When applied to controller or action, restricts access to unauthorized users or
	/// users that does not have roles specified.
	/// Any user with role "Administator" already has access!
	/// If request is ajax then EmptyResult is returned.
	/// Is request is normal then RedirectResult (if not authorized) or AccessDeniedResult is returned.
	/// </summary>
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited=true, AllowMultiple=false)]
	public class CmsAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
	{
		/// <summary>
		/// Gets allow roles list.
		/// </summary>
		public List<string> Roles { get; private set; }

		/// <summary>
		/// Initializes new attribute instance. Access is granted if user is logged in.
		/// </summary>
		public CmsAuthorizeAttribute()
		{
			Roles = new List<string>();
		}

		/// <summary>
		/// Initializes new attribute instance.
		/// </summary>
		/// <param name="roles">Allow roles list. Access is granted if user has any of this roles.</param>
		public CmsAuthorizeAttribute(params string[] roles)
		{
			Roles = new List<string>(roles);
		}

		/// <summary>
		/// Initializes new attribute instance.
		/// </summary>
		/// <param name="roles">Allow roles list. Access is granted if user has any of this roles.</param>
		public CmsAuthorizeAttribute(params CmsRoles[] roles)
		{
			Roles = roles.Select(r => r.ToString()).ToList();
		}

		/// <summary>
		/// Initializes new attribute instance.
		/// </summary>
		/// <param name="roles">Allow roles list. Access is granted if user has any of this roles.</param>
		public CmsAuthorizeAttribute(params object[] roles)
		{
			Roles = roles.Select(r => r.ToString()).ToList();
		}

		/// <summary>
		/// Returns roles array that is checked.
		/// </summary>
		public string[] GetAllowRoles()
		{
			// no roles
			if (Roles.Count == 0)
				return new string[0];
			
			// there is no administrator
			string admin = CmsRoles.Administrator.ToString().ToLower();
			if (!Roles.Any(r => r.ToLower() == admin))
			{
				List<string> roles = new List<string>(Roles);
				roles.Insert(0, CmsRoles.Administrator.ToString());
				return roles.ToArray();
			}

			// return current array
			return Roles.ToArray();
		}
		
		/// <summary>
		/// Executes filter.
		/// </summary>
		/// <param name="filterContext">Filter context.</param>
		public void OnAuthorization(AuthorizationContext filterContext)
		{
			if (filterContext == null)
				throw new ArgumentNullException("filterContext");

			IPrincipal usr = filterContext.HttpContext.User;
			CmsController c = filterContext.Controller as CmsController;

			// check authorized
			if (usr.Identity == null || !usr.Identity.IsAuthenticated || (c != null && c.User == null))
				filterContext.Result = new HttpUnauthorizedResult();
			// check access elements
			else if (c != null && !HasAccess(c.User))
				filterContext.Result = new HttpStatusCodeResult(403);
		}

		/// <summary>
		/// Determines whether user has access to method or controller marked with this attribute.
		/// </summary>
		/// <param name="user">User to check access for.</param>
		/// <returns>True, if user has access. Otherwise, false.</returns>
		public bool HasAccess(User user)
		{
			// anonymous are denied
			if (user == null)
				return false;
			// if no roles => all authorized has access
			else if (Roles.Count == 0)
				return true;

			// or search access in data provider
			foreach (string r in GetAllowRoles())
			{
				if (user.IsInRole(r))
					return true;
			}

			return false;
		}

	}
}
